// $Id: main.cpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2008-2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <boost/cstdlib.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/program_options.hpp>
#include <xpgen/init.hpp>
#include <xpgen/input_xpt.hpp>
#include <xpgen/model.hpp>
#include <xpgen/native_type.hpp>
#include <xpgen/output_header.hpp>
#include <xpgen/output_impl_header.hpp>
#include "config_file.hpp"
#include "version.hpp"

using namespace std;
namespace fs = boost::filesystem;
namespace po = boost::program_options;

static void print_usage()
{
    cout << "Usage: " << XPTPLUS_NAME << " [options] input-file1 [input-file2] [input-file3] ..." << endl;
}

static void print_help(const po::options_description& opts)
{
    print_usage();
//    cout << endl;
    cout << opts;
}

static void print_version()
{
    cout << "XPT to header file generator for xpcomplus library, version "
         << XPTPLUS_VERSION_STR << endl;
    cout << "Copyright 2008-2009 Joseph Wu" << endl;
    cout << "This program is free software; see the source for copying conditions." << endl;
}

static int process(int argc, char* argv[])
{
    xpgen::input_xpt input;
    xpgen::output_header output;
    xpgen::output_impl_header output_impl(&output);
    xpgen::native_type type;

    bool verbose = false;
    bool no_impl = false;
    string config_file;
    string input_dir;
    string output_dir;
    vector<string> input_files;
    vector<string> included_interfaces;
    vector<string> excluded_interfaces;

    po::options_description generic_opts("Allowed options");
    generic_opts.add_options()
        ("version", "print program version")
        ("help,h", "print this message")
        ("config-file,f", po::value<string>(&config_file), "configuration file")
        ("input-dir,i", po::value<string>(&input_dir)->default_value("."), "input directory")
        ("output-dir,o", po::value<string>(&output_dir)->default_value("."), "output directory")
        ("output-extension,x", po::value<string>(&output.output_ext)->default_value("hpp"), "output file extension")
        ("namespace", po::value<string>(&input.ns_override), "override namespace of interfaces (xxx.xxx.xxx)")
        ("indent", po::value<unsigned>(&output.indent_size)->default_value(4), "output indentation size")
        ("native-types,n", po::bool_switch(&type.enable), "map XPCOM types to native types")
        ("raw-interfaces,R", po::bool_switch(&output.raw_interfaces), "write raw interfaces only")
        ("no-impl,N", po::bool_switch(&no_impl), "do not generate impl headers")
        ("upper-constant", po::bool_switch(&output.upper_constant), "make constant name uppercase")
        ("upper-method,u", po::bool_switch(&output.upper_method), "make first letter of method name uppercase")
        ("write-scriptable", po::bool_switch(&output.write_scriptable), "write XPCOM_SCRIPTABLE to interfaces and methods")
        ("exact-includes", po::bool_switch(&output.exact_includes), "write includes exactly instead of <xpcom.hpp>")
        ("guess-notxpcom-result,r", po::bool_switch(&input.guess_notxpcom_result), "guess return of [notxpcom] method for nsresult")
        ("verbose,v", po::bool_switch(&verbose), "verbose mode")
        ;

    po::options_description hidden_opts("Hidden options");
    hidden_opts.add_options()
        ("input-file", po::value<vector<string> >(&input_files)->composing(), "input file")
    ;

    po::options_description config_opts("Configuration options");
    config_opts.add_options()
        ("type.auto", po::bool_switch(&type.auto_config)->default_value(true), "map native types automatically if not explicitly defined")

        ("type.int8", po::value<string>(&type.type_int8), "native type for int8")
        ("type.int16", po::value<string>(&type.type_int16), "native type for int16")
        ("type.int32", po::value<string>(&type.type_int32), "native type for int32")
        ("type.int64", po::value<string>(&type.type_int64), "native type for int64")
        ("type.uint8", po::value<string>(&type.type_uint8), "native type for uint8")
        ("type.uint16", po::value<string>(&type.type_uint16), "native type for uint16")
        ("type.uint32", po::value<string>(&type.type_uint32), "native type for uint32")
        ("type.uint64", po::value<string>(&type.type_uint64), "native type for uint64")
        ("type.float", po::value<string>(&type.type_float), "native type for float")
        ("type.double", po::value<string>(&type.type_double), "native type for double")
        ("type.char", po::value<string>(&type.type_char), "native type for char")
        ("type.wchar", po::value<string>(&type.type_wchar), "native type for wchar")
        ("type.pstring", po::value<string>(&type.type_pstring), "native type for pstring")
        ("type.pwstring", po::value<string>(&type.type_pwstring), "native type for pwstring")
        ("type.size", po::value<string>(&type.type_size), "native type for size")

        ("include-interface", po::value<vector<string> >(&included_interfaces), "included interfaces")
        ("exclude-interface", po::value<vector<string> >(&excluded_interfaces), "excluded interfaces")
        ;

    po::options_description cmdline_opts;
    cmdline_opts.add(generic_opts).add(hidden_opts);

    po::options_description config_file_opts;
    config_file_opts.add(generic_opts).add(config_opts).add(hidden_opts);

    po::positional_options_description pos_opts;
    pos_opts.add("input-file", -1);

    po::variables_map vm;
    po::store(po::command_line_parser(argc, argv).
            options(cmdline_opts).positional(pos_opts).run(), vm);
    po::notify(vm);

    if (!config_file.empty()) {
        ifstream is(config_file.c_str());
        po::store(xpt_parse_config_file(is, config_file_opts), vm);
        notify(vm);
    }

    // usage/help/version messages
    if (vm.count("help")) {
        print_help(generic_opts);
        return boost::exit_success;
    }

    if (vm.count("version")) {
        print_version();
        return boost::exit_success;
    }

    // input file must be specified
    if (!vm.count("input-file")) {
        print_usage();
        return boost::exit_success;
    }

    // verbose
    input.verbose = verbose;
    output.verbose = verbose;

    // input-dir, output-dir
    input.input_dir = input_dir;
    input.input_dir.normalize();
    output.output_dir = output_dir;
    output.output_dir.normalize();

    // native types
    type.init_output(output);

    // include/exclude
    output.included_interface_qnames.insert(
            included_interfaces.begin(), included_interfaces.end());
    output.excluded_interface_qnames.insert(
            excluded_interfaces.begin(), excluded_interfaces.end());

    // disable output_impl if raw-interfaces or no-impl is given
    if (output.raw_interfaces || no_impl) {
        output_impl.disable();
    }

    // do process, for each input file
    BOOST_FOREACH(const string& s, input_files)
    {
        if (verbose) {
            cout << "======== Begin process file " << s << " ========" << endl;
        }

        try {
            // Make input_file
            fs::path input_file = s;
            if (input_file.is_complete()) {
                input.input_file = input_file;
            } else {
                input.input_file = input.input_dir / input_file;
            }
            input.input_file.normalize();
            output.input_file = input.input_file;

            xpgen::module_decl module;
            input.do_all(&module);
            output.do_all(&module);

        } catch (const exception& e) {
            cerr << "Error: " << e.what()
                 << " (" << input.input_file.file_string() << ")" << endl;
        } catch (...) {
            cerr << "Error: unknown ("
                 << input.input_file.file_string() << ")" << endl;
        }

        if (verbose) {
            cout << "======== End process file " << s << " ========" << endl;
        }
    }

    return boost::exit_success;
}

int main(int argc, char* argv[])
{
    try {
        xpgen::initializer init;
        return process(argc, argv);

    } catch (const exception& e) {
        cerr << "Error: " << e.what() << endl;
        return boost::exit_failure;
    } catch (...) {
        cerr << "Error: unknown" << endl;
        return boost::exit_failure;
    }
}
